{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<center>\n",
    "<img src=\"../../img/ods_stickers.jpg\">\n",
    "## Открытый курс по машинному обучению. Сессия № 3\n",
    "\n",
    "### <center> Автор материала: Жеребцов Алексей "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## <center> Индивидуальный проект по анализу данных </center>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**План исследования**\n",
    " - Описание набора данных и признаков\n",
    " - Первичный анализ признаков\n",
    " - Первичный визуальный анализ признаков\n",
    " - Закономерности, \"инсайты\", особенности данных\n",
    " - Предобработка данных\n",
    " - Создание новых признаков и описание этого процесса\n",
    " - Кросс-валидация, подбор параметров\n",
    " - Построение кривых валидации и обучения \n",
    " - Прогноз для тестовой или отложенной выборки\n",
    " - Оценка модели с описанием выбранной метрики\n",
    " - Выводы\n",
    " \n",
    " Более детальное описание [тут](https://goo.gl/cJbw7V)."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Часть 0. Импорт библиотек ##"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "import pandas as pd\n",
    "import seaborn as sns\n",
    "from statsmodels.stats.weightstats import *\n",
    "from matplotlib import pyplot as plt\n",
    "from sklearn.feature_extraction.text import CountVectorizer\n",
    "import scipy.stats as st\n",
    "from sklearn.model_selection import train_test_split\n",
    "from sklearn.preprocessing import OneHotEncoder\n",
    "from scipy.sparse import csr_matrix, hstack\n",
    "from sklearn.linear_model import Ridge, Lasso, SGDRegressor\n",
    "from sklearn.model_selection import GridSearchCV, KFold, validation_curve, learning_curve\n",
    "from sklearn.metrics import mean_absolute_error, median_absolute_error, r2_score\n",
    "\n",
    "import warnings\n",
    "warnings.filterwarnings('ignore')\n",
    "%matplotlib inline"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "##  Часть 1. Описание набора данных и признаков"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Источник данных: ###\n",
    "Для исследования использован датасет с [kaggle](https://www.kaggle.com/philipjames11/dark-net-marketplace-drug-data-agora-20142015). В датасете содержится более 100000 обьявлений, размёщенных на торговой онлайн-площадке Agora (доступ возможен был через Tor) в период с 2014 по 2015 гг. На данный момент онлайн-площадка не функционирует."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Цели и задачи: ###\n",
    "#### Цель: ####\n",
    "Предсказать стоимость указанную в обьявлении по остальным признакам\n",
    "#### Задачи: ####\n",
    "    1 Проанализировать признаки обьявления в датасете\n",
    "    2 Отобрать наиболее значимые в прогнозировании цены\n",
    "    3 Построить и обучить модель восстановления регрессии для цены"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Признаки: ###\n",
    "#### Целевой признак:\n",
    "    Price - стоимость товара или услуги, указанная в объявлении\n",
    "#### Объясняющие:\n",
    "    Vendor - аккаунт с которого размещено объявление\n",
    "    Category - Категория, к которой относится обьявление (имеет вложенную иерархическую структуру /.../.../... до 4 уровней в глубину)\n",
    "    Item - название обьявления\n",
    "    ItemDescription - описание обьявления\n",
    "    Origin - локация происхождения обьявления\n",
    "    Destination - локация назначения обьявления\n",
    "    Rating - рэйтинг аккаунта с которого размещено обьявление\n",
    "    Remarks - дополнительные замечание (в датасете они либо пустые, либо \"Average price may be skewed outliar > .5 BTC found\")\n",
    "    "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "DATA_PATH='../../data/Agora.csv'"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "data=pd.read_csv(DATA_PATH, encoding=\"latin1\")\n",
    "data.columns = [x.replace(\" \", \"\") for x in data.columns]\n",
    "data.head()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "##  Часть 2. Первичный анализ признаков ##"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Получение общей информации по  датасету: ###"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "data.info()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Видно, что признаки Vendor, Category, Item, ItemDescription, Rating и целевая переменная Price не содержат,\n",
    "или содержат очень малое количество пропусков в данных. Обьявление с пропусками в этих признаках удалим,\n",
    "из-за их незначительного количества они не должны существенно повлиять на дальнейшее исследование. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Remove nan for Item, ItemDescription, Rating and Price\n",
    "data = data[pd.notnull(data['Item'])]\n",
    "data = data[pd.notnull(data['ItemDescription'])]\n",
    "data = data[pd.notnull(data['Price'])]\n",
    "data = data[pd.notnull(data['Rating'])]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Числовые признаки: ###\n",
    "Обработаем и переведём признаки Price и Rating в числовой формат:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#Price to numeric\n",
    "data['Price']=data['Price'].str.replace('BTC', '')\n",
    "data['Price']=data['Price'].str.replace(' ', '')\n",
    "data=data[~data['Price'].str.contains(r'[^0123456789.]')]\n",
    "data['Price'] = pd.to_numeric(data['Price'])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "При обработке рейтинга следует учесть, что есть продавцы, у которых на момент обьявления 0 сделок ([0 deals]) либо количество сделок очень маленькое для составления рейтинга. Создадим для таких продавцов отдельный признак 'No_Deals' - который будет равняться 1 в случае малого числа сделок и 0 в противоположном случае. Рейтинг же таких продавцов сделаем равным среднему значению по всему датасету."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#Rating to numeric and create No_deals\n",
    "data['Rating']= data['Rating'].str.split('/').str[0]\n",
    "data['Rating']=data['Rating'].str.replace(r'(~)','')\n",
    "data['No_deals']=0\n",
    "data.loc[data[data['Rating'].str.contains(r'(deal)')].index,['No_deals']]=1\n",
    "mean_rating=pd.to_numeric(data[~data['Rating'].str.contains(r'(deal)')]['Rating']).mean()\n",
    "data.loc[data[data['Rating'].str.contains(r'(deal)')].index,['Rating']]=str(mean_rating)\n",
    "data['Rating']=data['Rating'].astype(float)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "data[['Rating','Price']].describe()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Категориальные признаки: ###\n",
    "Под категориальные признаки в исследуемом датасете попадают Vendor, Category, Origin, Destination."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Признак Category содержит вложенный список подкатегорий, глубиной до 4-х. Разобьём подкатегории на 4 отдельных признака (subcat1/subcat2/subcat3/subcat4)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "data = pd.concat([data,data.Category.str.split('/', expand=True)], axis=1)\n",
    "data = data.rename(columns={0: 'subcat1', 1: 'subcat2',2: 'subcat3', 3: 'subcat4'})\n",
    "data=data.drop(['Category'],axis=1)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Обработаем признаки Origin и Destination"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "for x in [\"Origin\",\"Destination\"]:\n",
    "    data[x] = data[x].str.capitalize().str.replace('[^\\w\\s]','') # 1\n",
    "    data[x] = data[x].str.replace(r\"\\bonly\\b\", '').str.strip() # 2 \n",
    "    data.loc[data[x].str.contains(r\"(?i)world|\\b(?i)word\\b|(?i)global\",na=False),x] = \"Worldwide\" # 3\n",
    "    data.loc[data[x].str.contains(r\"(?i)kingdom\",na=False),x] = \"Uk\" # 4\n",
    "    data.loc[data[x].str.contains(r\"(?i)(\\bunited states\\b)|\\b(?i)us\\b\",na=False),x] = \"Usa\" # 5\n",
    "data['Origin']=data['Origin'].fillna('OriginNotDefined')\n",
    "data['Destination']=data['Destination'].fillna('DestNotDefined')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "data[['Vendor','subcat1','subcat2','subcat3','subcat4','Origin','Destination']].describe()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Текстовые признаки ###\n",
    "Под текстовые признаки попадают Item и ItemDescription. Проведём довольно грубую обаботку текстовых данных, убрав все знаки, не являющиеся буквами или цифрами."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "data['Item']=data['Item'].str.replace(r'[^0-9a-zA-Z ]','')\n",
    "data['ItemDescription']=data['ItemDescription'].str.replace(r'[^0-9a-zA-Z ]','')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Бинарные признаки ###\n",
    "Под бинарный признак попадает вышесозданный нами признак No_Deals, а так же приведём к бинарному виду признак Remarks, который судя по описанию либо пустой, либо говорит о том что средняя цена может быть искажена."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "data['Remarks']=data['Remarks'].fillna('0')\n",
    "data.loc[data[data['Remarks']!='0'].index,['Remarks']]='1'\n",
    "data['Remarks']=pd.to_numeric(data['Remarks'])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "data.groupby('No_deals')['Price'].median()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "data.groupby('Remarks')['Price'].median()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "##  Часть 3. Первичный визуальный анализ признаков"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Целевой признак: ###\n",
    "Построим распределение целевого признака"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "sns.distplot(data['Price'],hist=False)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "В распределении присутствуют очень большие цены. Причина скорее всего в том, что не везде цены указаны в BTC. Исходя из этого предположения попробуем отфильтровать выбросы, находящиеся выше 95% квантили. Так же слишком маленькие цены могут проставляться, в случае когда цена договорная. Поэтому отфильтруем выбросы, находящиеся ниже 5% квантили"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "quantiles=data['Price'].quantile([0.05,0.95])\n",
    "sns.distplot(data[(data['Price']<quantiles[0.95])&(data['Price']>quantiles[0.05])]['Price'],hist=False)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Распределение стало более \"гладким\", так что скорее всего наше предположение о выбросах верно. Попутно удалим эти выбросы из исходного датасета. И сравним логарифм нашего распределения цены с нормальным распределением."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#Delete quantiles\n",
    "data=data[(data['Price']<quantiles[0.95])&(data['Price']>quantiles[0.05])]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "sns.distplot(data['Price'].map(np.log),fit=st.norm)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print('p-value ', st.shapiro(data['Price'].map(np.log))[1]*100, '%')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "На первый взгляд логарифм нашего распределение цены напоминает нормальное распределение, однако тест Шапиро-Уилка не позволяет статистически принять эту гипотезу."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Зависимость цены от происхождения('Origin') и назначения('Destination'):"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "Top_Origin=data.groupby('Origin')['Price'].count().sort_values(ascending=False).axes[0].values[:15]\n",
    "Top_Destination=data.groupby('Destination')['Price'].count().sort_values(ascending=False).axes[0].values[:15]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "f, (count_orig, count_dest) = plt.subplots(1,2,figsize=(13,5))\n",
    "count_orig=sns.countplot(y='Origin',data=data[data['Origin'].isin(Top_Origin)],ax=count_orig, order=Top_Origin)\n",
    "count_dest=sns.countplot(y='Destination',data=data[data['Destination'].isin(Top_Destination)],ax=count_dest, order=Top_Destination)\n",
    "plt.tight_layout(pad=0)\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "f, (box_orig, box_dest) = plt.subplots(1,2,figsize=(13,5))\n",
    "box_orig=sns.boxplot(x='Price',y='Origin',data=data[data['Origin'].isin(Top_Origin)],ax=box_orig, order=Top_Origin)\n",
    "box_dest=sns.boxplot(x='Price',y='Destination',data=data[data['Destination'].isin(Top_Destination)],ax=box_dest, order=Top_Destination)\n",
    "plt.tight_layout(pad=0)\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "sort_subcat1=data.groupby('subcat1')['Price'].count().sort_values(ascending=False).axes[0].values"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Распределение цены в зависимости от категории\n",
    "Вначале построим график числа обьявлений в зависимости от категории, затем построим график распределения цены в зависимости от категории"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "fig, axes = plt.subplots(figsize=(16, 10))\n",
    "sns.countplot(y='subcat1',data=data,order=sort_subcat1)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "fig, axes = plt.subplots(figsize=(16, 10))\n",
    "sns.boxplot(x='Price',y='subcat1',data=data,order=sort_subcat1)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Для 5 наиболее встречаемых категорий построим зависимость распределения цены от подкатегории subcat2"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "fig, axes = plt.subplots(nrows=5, ncols=1, figsize=(16, 30))\n",
    "for idx,category in enumerate(sort_subcat1[:5]):\n",
    "    sns.boxplot(x='Price',y='subcat2',data=data[data['subcat1']==category], ax=axes[idx])\n",
    "    axes[idx].set_ylabel(category)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Числовые признак рэйтинг('Rating'):\n",
    "Построим распределение числового признака"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "sns.distplot(data['Rating'],hist=False)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Видно, что это усреднённые дискретные значения от 1 до 5. Округлим рейтинг до целых чисел, получив таким образом вместо числового, категориальный признак."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "data['Rating']=data['Rating'].apply(round)\n",
    "data['Rating']=data['Rating'].astype(str)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "sns.countplot(x='Rating',data=data)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Видно что в большинстве случаев рейтин продавцов близок к максимальному."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "##  Часть 4. Закономерности, \"инсайты\", особенности данных"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Из анализа признаков можно сделать следующие выводы:\n",
    "    1. Значительная часть пользователей находится в США\n",
    "    2. Большинство обьявлений связано с наркотиками\n",
    "    3. Медианная цена умеренно варьируется в зависимости от страны происхождения\n",
    "    4. Медианная цена значительно варьирутеся в зависимости от категории и подкатегории\n",
    "    5. Как по странам так и по категориям данные содержат значительное число ценовых выбросов\n",
    "    6. Третий и четвертый уровень категорий отмечен в незначительном числе обьявлений, его можно не принимать во внимание\n",
    "    7. Для нового бинарного признака No_deals медианная цена изменяется незначительно\n",
    "    8. Для бинарного признака Remarks медианная цена тличается значительно\n",
    "    9. Присутствуют текстовые признаки, в которых содержится информация, которая должна влиять на цену\n",
    "    10. Небольшой прирост медианной цены заметен либо в далёких локация, таких как австралия, либо если место назначения не имеет значения"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "##  Часть 5. Предобработка данных "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Разобьём данные на признаки и целевую переменную"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "y=data['Price']\n",
    "X=data.drop(['Price'],axis=1)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Удалим subcat3 и subcat4, про них говорилось выше. Пропуски во второй подкатегории заменим на 'Other'"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "X=X.drop(['subcat3','subcat4'],axis=1)\n",
    "X['subcat2']=X['subcat2'].fillna('Other')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Разобьём данные на тренировочные и тестовые"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "X.info()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Извлечение текстовых признаков Item и ItemDescription"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "I_count_vect=CountVectorizer(ngram_range=(1, 2),max_features=100000)\n",
    "X_train_Item=I_count_vect.fit_transform(X_train['Item'])\n",
    "X_test_Item=I_count_vect.transform(X_test['Item'])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "Id_count_vect=CountVectorizer(ngram_range=(1, 2),max_features=100000)\n",
    "X_train_ItemDesc=Id_count_vect.fit_transform(X_train['ItemDescription'])\n",
    "X_test_ItemDesc=Id_count_vect.transform(X_test['ItemDescription'])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "X_train_text=csr_matrix(hstack((X_train_Item,X_train_ItemDesc)))\n",
    "X_test_text=csr_matrix(hstack((X_test_Item,X_test_ItemDesc)))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Извлечение признаков по категориям"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#Vendor\n",
    "Vend_count_vect=CountVectorizer()\n",
    "X_train_Vend=Vend_count_vect.fit_transform(X_train['Vendor'])\n",
    "X_test_Vend=Vend_count_vect.transform(X_test['Vendor'])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#subcat1\n",
    "Sub1_count_vect=CountVectorizer()\n",
    "X_train_Sub1=Sub1_count_vect.fit_transform(X_train['subcat1'])\n",
    "X_test_Sub1=Sub1_count_vect.transform(X_test['subcat1'])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#subcat2\n",
    "Sub2_count_vect=CountVectorizer()\n",
    "X_train_Sub2=Sub2_count_vect.fit_transform(X_train['subcat2'])\n",
    "X_test_Sub2=Sub2_count_vect.transform(X_test['subcat2'])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#Origin\n",
    "Orig_count_vect=CountVectorizer()\n",
    "X_train_Orig=Orig_count_vect.fit_transform(X_train['Origin'])\n",
    "X_test_Orig=Orig_count_vect.transform(X_test['Origin'])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#Destination\n",
    "Dest_count_vect=CountVectorizer()\n",
    "X_train_Dest=Dest_count_vect.fit_transform(X_train['Destination'])\n",
    "X_test_Dest=Dest_count_vect.transform(X_test['Destination'])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#Rating\n",
    "RatingOhe=OneHotEncoder()\n",
    "X_train_Rating=RatingOhe.fit_transform(X_train['Rating'].values.reshape(-1, 1))\n",
    "X_test_Rating=RatingOhe.transform(X_test['Rating'].values.reshape(-1, 1))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "X_train_categ=csr_matrix(hstack((X_train_Vend,X_train_Sub1,X_train_Sub2,X_train_Orig,X_train_Dest,X_train_Rating)))\n",
    "X_test_categ=csr_matrix(hstack((X_test_Vend,X_test_Sub1,X_test_Sub2,X_test_Orig,X_test_Dest,X_test_Rating)))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Построим общую sparse матрицу для всех обучающих и тестовых признаков"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "X_train_sparse=csr_matrix(hstack((X_train_text,X_train_categ,X_train[['No_deals','Remarks']])))\n",
    "X_test_sparse=csr_matrix(hstack((X_test_text,X_test_categ,X_test[['No_deals','Remarks']])))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Прологарифмируем целевой признак, т.к. распределение его логарифма более похоже на нормальное"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "y_train=np.log(y_train)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "y_test=np.log(y_test)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "##  Часть 6. Создание новых признаков и описание этого процесса"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "При первичном анализе данных в части 1 был получен новый бинарный признак No_deals и описан этот процесс"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "##  Часть 7. Кросс-валидация, подбор параметров"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "В сформированном окончательном наборе данных мы получили очень большое число разреженных признаков, относительно большую выборку данных, плюс среди данных имеется некоторое количество выбросов, опираясь на эту особенность попробуем построить модель восстановления регрессии на основе SGDRegressor. В качестве функции потерь выберем функцию Губера. За метрику ошибки возьмём r2_score"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "kf = KFold(n_splits=3, shuffle=True, random_state=18)\n",
    "\n",
    "model = SGDRegressor(loss='huber',random_state=18, learning_rate='optimal', max_iter=100)\n",
    "\n",
    "params = {'alpha': np.linspace(0.000005,0.0001, num=10)}\n",
    "\n",
    "grid = GridSearchCV(model, param_grid=params, scoring='r2', cv=kf, verbose=3)\n",
    "grid.fit(X_train_sparse,y_train)\n",
    "grid.best_score_"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "grid.best_estimator_"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "##  Часть 8. Построение кривых валидации и обучения "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "alphas = np.linspace(0.000005,0.0001, num=10)\n",
    "val_train, val_test = validation_curve(model, X_train_sparse, y_train, param_name='alpha', param_range=alphas, cv=kf, scoring='r2')\n",
    "\n",
    "def plot_with_err(x, data, **kwargs):\n",
    "    mu, std = data.mean(1), data.std(1)\n",
    "    lines = plt.plot(x, mu, '-', **kwargs)\n",
    "    plt.fill_between(x, mu - std, mu + std, edgecolor='none',\n",
    "                     facecolor=lines[0].get_color(), alpha=0.2)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "train_sizes = np.linspace(0.05, 1, 10)\n",
    "N_train, tr_train, tr_test = learning_curve(model, X_train_sparse, y_train, train_sizes=train_sizes, cv=kf, scoring='r2')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "fig = plt.figure(figsize=(20, 5))\n",
    "ax1 = fig.add_subplot(121)\n",
    "ax1.set_title('Кривая Валидации')\n",
    "plot_with_err(alphas, val_train, label='training scores')\n",
    "plot_with_err(alphas, val_test, label='validation scores')\n",
    "plt.xlabel(r'$\\alpha$'); plt.ylabel('r^2')\n",
    "plt.legend();\n",
    "\n",
    "\n",
    "ax2 = fig.add_subplot(122)\n",
    "ax2.set_title('Кривая Обучения')\n",
    "plot_with_err(N_train, tr_train, label='training scores')\n",
    "plot_with_err(N_train, tr_test, label='validation scores')\n",
    "plt.xlabel('Training Set Size'); plt.ylabel('r^2')\n",
    "plt.legend()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "##  Часть 9. Прогноз для тестовой или отложенной выборки"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "r2_score(y_test, grid.predict(X_test_sparse))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "##  Часть 10. Оценка модели с описанием выбранной метрики"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Судя по кривым валидации имеются небольшие признаки переобучения,однако прогноз на тестовой выборке оказался рядом с лучшим прогнозом на кроссвалидации. Судя по кривой обучения, при данном наборе признаков возможно недостаточно данных для обучения, т.к. качество модели рассчитанное по r2_score приемлимо, но недостаточно. r2_score метрика была выбрана, т.к. мы имеем выбросы в разбросе цен, она позволяет их лучше учесть, при оценке качества модели."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Часть 11. Выводы "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Полученная модель имеет предсказание оценки качества приемлимое, но недостаточное. Возможно необходим больший набор данных. Так же в этом датасете очень важными являются признаки содержащиеся в текстовых полях Item и ItemDescription, в рассматриваемом исследовании они были извлечены недостаточно информативно."
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.5.2"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 1
}
